﻿using Tetris.Game.ControlsBase;
using Tetris.Game.ControlsBase.Common;

namespace Tetris.Game.Common.TetrisCenter.TetrisBase
{
    /// <summary>
    /// 形状
    /// 每一个形状由四个方块组成于_blockControls数组中
    /// </summary>
    public unsafe class BlockShapeBase : IGameProperty, IDisposable
    {
        private BlockControl[] _blockControls;
        private int _curIndex = 0;
        public BlockShapeBase()
        {
            _blockControls = new BlockControl[4];
            Id = Guid.NewGuid().ToString();
            IsMove = true;
        }
        /// <summary>
        /// 方块集合
        /// </summary>
        public BlockControl[] BlockControls { get { return _blockControls; } }
        /// <summary>
        /// 形状类型
        /// </summary>
        public ShapeType TetrisShapeType { get; set; }
        /// <summary>
        /// 是否可以移动
        /// </summary>
        public bool IsMove { get; set; }
        /// <summary>
        /// 方块大小
        /// </summary>
        public int Size { get; set; }
        /// <summary>
        /// 行数
        /// </summary>
        public int Row { get; set; }
        /// <summary>
        /// 列数
        /// </summary>
        public int Column { get; set; }
        /// <summary>
        /// 当前形状唯一码
        /// </summary>
        public string Id { get; set; }
        /// <summary>
        /// 中心坐标
        /// </summary>
        public Point Center
        {
            get
            {
                fixed (BlockControl* block = this.BlockControls)
                {
                    int sumX = 0, sumY = 0;
                    for (int i = 0; i < BlockControls.Length; i++)
                    {
                        BlockControl* curBlock = block + i;
                        sumX += curBlock->PositionX;
                        sumY += curBlock->PositionY;
                    }
                    return new Point((int)Math.Round(sumX / 4f, 0), (int)Math.Round(sumY / 4f, 0));
                }
            }
        }
        /// <summary>
        /// 更新坐标回调
        /// </summary>
        public Action<BlockControl> UpdateLocationCallback { get; set; }
        /// <summary>
        /// 检验消除行回调
        /// </summary>
        public Action CheckEraseRowsCallback { get; set; }
        /// <summary>
        /// 游戏结束回调
        /// </summary>
        public Action GameOverCallback { get; set; }
        /// <summary>
        /// 创建形状
        /// </summary>
        public virtual void CreationBlock()
        {

        }
        /// <summary>
        /// 创建方块回调
        /// </summary>
        /// <param name="action"></param>
        protected virtual void CreationBlock(Action<BlockControl> action)
        {
            BlockControl blockControl = new BlockControl();
            blockControl.TetrisShapeType = TetrisShapeType;
            blockControl.Size = new Size(Size, Size);
            blockControl.GroupId = Id;
            blockControl.IsMove = this.IsMove;
            action(blockControl);

            fixed (BlockControl* arr = _blockControls)
            {
                *(arr + _curIndex++) = blockControl;
            }
        }
        /// <summary>
        /// 移动
        /// </summary>
        /// <param name="blocks">二维数组方块</param>
        /// <param name="direction">移动方向</param>
        /// <returns></returns>
        public bool Move(BlockControl[,] blocks, DirectionType direction)
        {
            int dx = 0, dy = 0;
            if (direction == DirectionType.Left) dx = -1;
            else if (direction == DirectionType.Right) dx = 1;
            else if (direction == DirectionType.Down) dy = 1;
            else return false;
            // 检验移动
            if (!CheckMove(blocks, dx, dy))
                return false;
            // 移动
            Move(blocks, dx, dy);
            // 播放音乐
            SoundType.Move.PlaySound();
            return true;
        }
        /// <summary>
        /// 检验移动
        /// </summary>
        /// <param name="blocks">二维数组方块</param>
        /// <param name="dx">横坐标变化量</param>
        /// <param name="dy">纵坐标变化量</param>
        /// <returns></returns>
        public bool CheckMove(BlockControl[,] blocks, int dx, int dy)
        {
            fixed (BlockControl* block = this.BlockControls, set = blocks)
            {
                for (int i = 0; i < this.BlockControls.Length; i++)
                {
                    BlockControl* curBlock = block + i;
                    int newX = curBlock->PositionX + dx;
                    int newY = curBlock->PositionY + dy;
                    // 检验横坐标越界
                    if (newX < 0 || newX >= Column)
                        return false;
                    // 检验纵坐标越界
                    if (newY < 0 || newY >= Row)
                    {
                        this.IsMove = false;
                        if (newY >= Row)
                            SoundType.Through.PlaySound();
                        // 检验是否可以消除行
                        CheckEraseRowsCallback?.Invoke();
                        return false;
                    }
                    BlockControl* curSet = set + newY * Column + newX;
                    // 判断当前方块和此二维方块中是否为同组别，如果不是说明不能移动了
                    if (*curSet != null && curSet->GroupId != curBlock->GroupId)
                    {
                        // 方块向下移动了一个单位，不能继续移动，停止移动
                        if (dy == 1)
                        {
                            this.IsMove = false;
                            // 播放音乐
                            SoundType.Through.PlaySound();
                            // 检验是否可以消除行
                            CheckEraseRowsCallback?.Invoke();
                        }
                        // 如果当前形状的纵坐标最小值为0，说明到顶了，游戏结束
                        if (this.GetMinPositionY(block) == 0)
                        {
                            SoundType.GameOver.PlaySound();
                            GameOverCallback?.Invoke();
                        }
                        return false;
                    }
                }
                return true;
            }
        }
        /// <summary>
        /// 获取当前形状最小的纵坐标
        /// </summary>
        /// <param name="block">当前形状</param>
        /// <returns></returns>
        public int GetMinPositionY(BlockControl* block)
        {
            int minY = block->PositionY;
            for (int i = 1; i < this.BlockControls.Length; i++)
            {
                BlockControl* curBlock = block + i;
                if (minY > curBlock->PositionY)
                    minY = curBlock->PositionY;
            }
            return minY;
        }
        public void Move(BlockControl[,] blocks, int dx, int dy)
        {
            int count = this.BlockControls.Length;
            fixed (BlockControl* block = this.BlockControls, set = blocks)
            {
                for (int i = count - 1; i >= 0; i--)
                {
                    BlockControl* curBlock = block + i;
                    BlockControl* curSet = set + curBlock->PositionY * Column + curBlock->PositionX;
                    // 有的方块先移动，导致有的方块先占领未移动方块的位置，如果未占领则把自己先前的位置置未空，否则不。
                    if (*curSet != null && curSet->Id == curBlock->Id)
                        *curSet = null;
                    // 更新坐标
                    curBlock->PositionX += dx;
                    curBlock->PositionY += dy;
                    curSet = set + curBlock->PositionY * Column + curBlock->PositionX;
                    // 更新自己在二维数组中的位置
                    *curSet = *curBlock;
                    if (UpdateLocationCallback != null)
                    {
                        Task.Run(() =>
                        {
                            UpdateLocationCallback(*curBlock);
                        });
                    }
                }
            }
        }
        /// <summary>
        /// 旋转
        /// </summary>
        /// <param name="blockControls">二维方块</param>
        /// <param name="rotationAngle">旋转角度，默认每次旋转90度</param>
        /// <returns></returns>
        public bool RotateShape(BlockControl[,] blockControls, RotationAngle rotationAngle = RotationAngle.Degrees90)
        {
            // 播放音乐
            SoundType.Move.PlaySound();
            if (this.TetrisShapeType == ShapeType.O) return false;// O形状不旋转，因为旋转后是一样的
            Point center = this.Center;
            int centerX = center.X, centerY = center.Y;
            // 检验是否可以旋转
            if (!this.IsRotationValid(blockControls, RotationAngle.Degrees90, centerX, centerY)) return false;
            // 旋转形状
            this.RotateShape(blockControls, rotationAngle, centerX, centerY);
            return true;
        }
        /// <summary>
        /// 旋转方块
        /// </summary>
        /// <param name="blockControls">二维方块</param>
        /// <param name="rotationAngle">角度</param>
        /// <param name="centerX">图像中心坐标X</param>
        /// <param name="centerY">图像中心坐标Y</param>
        public void RotateShape(BlockControl[,] blockControls, RotationAngle rotationAngle, int centerX, int centerY)
        {
            int count = this.BlockControls.Length;
            fixed (BlockControl* setPointer = blockControls, block = this.BlockControls)
            {
                for (int i = count - 1; i >= 0; i--)
                {
                    BlockControl* curBlock = block + i;
                    // 计算旋转后的坐标
                    var point = rotationAngle.GetRotatePoint(curBlock->PositionX, curBlock->PositionY, centerX, centerY);
                    // 优化：旋转后，方块本身不一定会改变位置；移动后坐标一定会改变。
                    if (curBlock->PositionX == point.X && curBlock->PositionY == point.Y) continue;
                    BlockControl* cur = setPointer + curBlock->PositionY * Column + curBlock->PositionX;
                    // 判断旋转前二维方块的值是否为当前未旋转的方块，如果是则致置为空，否则不；因为可能存在某些方块先旋转，把未旋转方块的位置给占领。
                    if (*cur != null && cur->Id == curBlock->Id)
                        *cur = null;
                    // 更新旋转后的方块坐标
                    curBlock->PositionX = point.X;
                    curBlock->PositionY = point.Y;
                    cur = setPointer + curBlock->PositionY * Column + curBlock->PositionX;
                    // 更新旋转后方块在二维数组中的值
                    *cur = *curBlock;
                }
            }
        }
        /// <summary>
        /// 检测是否可以旋转
        /// </summary>
        /// <param name="blocks">二维方块</param>
        /// <param name="angle">角度</param>
        /// <param name="centerX">图像中心坐标X</param>
        /// <param name="centerY">图像中心坐标Y</param>
        /// <returns></returns>
        public bool IsRotationValid(BlockControl[,] blocks, RotationAngle angle, int centerX, int centerY)
        {
            fixed (BlockControl* block = this.BlockControls, set = blocks)
            {
                int newX, newY;
                for (int i = 0; i < BlockControls.Length; i++)
                {
                    BlockControl* curBlock = block + i;
                    // 计算旋转后的坐标
                    var point = angle.GetRotatePoint(curBlock->PositionX, curBlock->PositionY, centerX, centerY);
                    newX = point.X;
                    newY = point.Y;
                    // 检测是否越界
                    if (newX < 0 || newX >= Column || newY < 0 || newY >= Row) return false;
                    BlockControl* curSet = set + newY * Column + newX;
                    // 因为一个形状由四个正方形组成，把这四个方块称为一组，他们的GroupId是相等的，每一个正方形方块的Id是唯一的。
                    // 此处判断二维数组中某位置的正方形方块是否为同组，不是同组说明产生碰撞。
                    if (*curSet != null && curSet->GroupId != curBlock->GroupId) return false;
                }
            }
            return true;
        }
        /// <summary>
        /// 旋转
        /// </summary>
        /// <param name="blocks"></param>
        public void RotateShape2(BlockControl[,] blockControls, RotationAngle rotationAngle, int centerX, int centerY)
        {
            /* 旋转和移动后在二维数组中更新位置问题：
             * 问题：A方块更新后在二维数组中的位置为A'，如果此时A’的位置是B，直接更新A'过去就将B覆盖掉，导致二维数组中不存在B；
             * 
             * 方案1（RotateShape2）：我们移动或旋转之前都做了校验，如果为true，说明肯定可以移动或旋转，那么我只有需要将A'的位置没有其它元素的直接更新，
             *        如果有但还是自己（cur->Id == curBlock->Id）不需要更新，否则添加缓存，后面循环再更新；后面一个一个的遍历查找新坐标A’有没有元素，
             *        有就下一个，没有就更新，直至list.Count == 0，因为可以移动或旋转，那肯定有解，while一定能出去，不会出现死循环。
             *        
             * 方案2（RotateShape）：思路是遍历BlockShapeBase对象中的BlockControls属性，直接获取每一个BlockControl，
             *        在更新他的PositionX与PositionY之前先将自己从二维数组中移除，移除之前先判断if(cur->Id == curBlock->Id)，如果为true才能移除，否则会出现移除其它方块，
             *        最后再更新PositionX与PositionY，再更新二维数组中的值。
             *  
             *  两个方案的区别：
             *                 1.前者创建了缓存对象，后者无；
             *                 2.前者先更新能更新的，后者直接更新。
             *                 
             *  注：旋转方法直接更新,保留了RotateShape2方法，留着给自己的纪念吧~~~
             */
            int count = this.BlockControls.Length;
            List<BlockControl> list = new List<BlockControl>(this.BlockControls.Length);
            fixed (BlockControl* setPointer = blockControls, block = this.BlockControls)
            {
                for (int i = count - 1; i >= 0; i--)
                {
                    BlockControl* curBlock = block + i;
                    var point = rotationAngle.GetRotatePoint(curBlock->PositionX, curBlock->PositionY, centerX, centerY);
                    curBlock->PositionX = point.X;
                    curBlock->PositionY = point.Y;
                    BlockControl* cur = setPointer + curBlock->PositionY * Column + curBlock->PositionX;
                    if (*cur == null)
                    {
                        *(setPointer + curBlock->GetLastPositionY * Column + curBlock->GetLastPositionX) = null;
                        *cur = *curBlock;
                    }
                    else
                    {
                        if (cur->Id != curBlock->Id)
                            list.Add(*curBlock);
                    }
                }
                int t = 0;
                while (list.Count != 0)
                {
                    var blockTemp = list[t];
                    BlockControl* cur = setPointer + blockTemp.PositionY * Column + blockTemp.PositionX;
                    if (*cur == null)
                    {
                        *(setPointer + blockTemp.GetLastPositionY * Column + blockTemp.GetLastPositionX) = null;
                        *cur = blockTemp;
                        list.RemoveAt(t);
                    }
                    else
                        ++t;
                    if (list.Count > 0 && t >= list.Count)
                        t = 0;
                }
            }

            list = null;
        }
        public void Dispose()
        {
            if (_blockControls == null)
                return;
            foreach (var item in _blockControls)
            {
                item?.ExecBeginInvoke(() =>
                {
                    item.DisposeEx();
                });
            }
            _blockControls = null;
        }

    }

}
